// Copyright lowRISC contributors.
// Licensed under the Apache License, Version 2.0, see LICENSE for details.
// SPDX-License-Identifier: Apache-2.0

#include <bitset>
#include <ostream>
#include <stdint.h>

#include "gmock/gmock.h"
#include "gtest/gtest.h"

// Reach into math_builtins.c and grab the internal testing symbols.
extern "C" {
int64_t _ot_builtin_lshift_i64(int64_t val, int32_t shift);
int64_t _ot_builtin_rshift_i64(int64_t val, int32_t shift);
int64_t _ot_builtin_ashift_i64(int64_t val, int32_t shift);

int32_t _ot_builtin_bswap_i32(int32_t val);
int _ot_builtin_popcount_i32(int32_t val);
int _ot_builtin_parity_i32(int32_t val);

int _ot_builtin_clz_i32(int32_t val);
int _ot_builtin_ctz_i32(int32_t val);
int _ot_builtin_find_first_i32(int32_t val);
}

namespace math_unittest {
namespace {

class ShiftTest : public testing::TestWithParam<std::tuple<uint64_t, int>> {};

TEST_P(ShiftTest, LShift) {
  auto val = std::get<0>(GetParam());
  auto sh = std::get<1>(GetParam());
  EXPECT_EQ(_ot_builtin_lshift_i64(val, sh), val << sh);
}

TEST_P(ShiftTest, RShift) {
  auto val = std::get<0>(GetParam());
  auto sh = std::get<1>(GetParam());
  EXPECT_EQ(_ot_builtin_rshift_i64(val, sh), val >> sh);
}

TEST_P(ShiftTest, AShift) {
  int64_t val = std::get<0>(GetParam());
  auto sh = std::get<1>(GetParam());
  EXPECT_EQ(_ot_builtin_ashift_i64(val, sh), val >> sh);
}

// clang-format off
constexpr uint64_t kShiftVectors[] = {
  0x72e31374b288d54b, 0xe3803f2635f6ee6f, 0xba25acc04b1c109f, 0xc47399918b992246,
  0xfcd337f4a9657652, 0xad1d516938ba9e58, 0xcaf2ea72ecab24ce, 0xe575c95799b3fd25,
  0x7179d63f5318a042, 0x66cb3b7bafa9ab91, 0x01e3a61671afb124, 0x883dec25388a7d17,
  0x2409a453b22f2afb, 0xa3a22aaf52b455a6, 0xe6aafa4b21df3fde, 0x1ab66cae8bf7f70a,
  0xa2c5ce0f7a8c39ae, 0xb4dfee68dc55a333, 0x424a9080c3eb7f28, 0x33e20b7ad01ed2d3,
  0x012986549ba5c18e, 0x71470e3eab601397, 0x7c641906111c4a97, 0x4947714cc88fa328,
  0x95ef5317cba3383a, 0x8cc65207c9a01c75, 0xbacd69c7b9f07da5, 0x7ea168d4980e39b1,
  0xfd434457e60eea3b, 0xfb06cbaefee8dac0, 0x59c6cbe506da5009, 0x17384e6055a26fda,
  0x5f7a2b0fb58b3d5b, 0x04581fca7eca14c5, 0xb0e85631e03a2f5f, 0x072ef34c9ffe2af0,
  0xb7a8416d262ab414, 0xaaeda113b5c0f99b, 0x5ec1edc1bb424e4c, 0xd7a6efda2c1adfd9,
  0x58c00ce60adee54e, 0x2738634afbe4fa68, 0xfbd84af315127e70, 0xd7b1c602bbc5f287,
  0xa87ed34fade4ca9e, 0x274c0d4536a73a4f, 0x6bd60d1fbd5dd8bf, 0x3e3480053ffa2b32,
  0xe4509fb8d04198c7, 0x1ca94e3528b69802, 0xc1db8afa638209dd, 0xf3b127563b4e67e6,
  0x55945fc85fce5a15, 0x2f46dfdd59dc99bb, 0xd305bd01c6fc90cb, 0xb6f3e737ae6852e2,
  0x23a1a2588da1b8bc, 0xa9e4f54d6aa91ab0, 0x754b06a594c37253, 0x530d325ad21250ad,
  0x9ba1df480099e563, 0xaeddb8f02430fa05, 0x95420781c1434885, 0x7ef252bb0353cc94,
  0x360b0516971db663, 0xd984feb6fb7f383d, 0xe76cb6124460dce4, 0x00b449c6afad532c,
  0x9549d9a4db6698bd, 0x472176983d94969d, 0x44203ca1c11f6698, 0x316b015904b9a585,
  0xef529bbe99f8789d, 0x6797c2899a4a6338, 0x5856d8199b694d0d, 0x40d8a06d9ea5d550,
  0x063371e154698005, 0xd0b4a74656dfacc3, 0x904d67ecddc9bdc1, 0xf3da1b4f14a8ee38,
  0xa7f2008e10ff5104, 0xc971d6076bb18705, 0x542052cbcf18e642, 0x8bc461ce47296918,
  0x050cd8d5a1d205f5, 0x96b852ad44647978, 0xf06eb96bb06c5014, 0x7f34e0824755f34d,
  0xef2bad1655aa4af3, 0xfa2a29654ec5cde7, 0x25c5b8b6be8210f9, 0xdb2abc045c3debf2,
  0xc87cd628ca86ee17, 0x754dcf0f9ef1f43f, 0x5b41d1713ec1d622, 0xf04e0ccadb55f484,
  0x91cc30dba5cb088a, 0x8fadd12094d86765, 0x4b32b8bf703b2bcb, 0x2b388b7267361d11,
  0x796c96e61c5cee34, 0xa422df223c6f4386, 0x2c73a57443bd8e55, 0x5055ba2568f0840e,
  0x2c12963cc6252411, 0x2866daa721519adf, 0x9cf9fc214a9bf499, 0xd2a1b710d03a83c8,
  0x4aaefeec333d2bc6, 0x74f426e6c662351d, 0xca9137650e7ff7ff, 0x81a59590069af3df,
  0x90413762d48e5fe4, 0x9acdf15570b4d51a, 0x7a18f080c38fbe68, 0x4819af998c5480b5,
  0x0b1ab546043e8c39, 0x5c41cd7dcd416951, 0x123138f887109c2d, 0xa75ed843bf10be7e,
  0xfd1361e852c595f0, 0xa9346bdc942d64db, 0xb090a120f73defb7, 0xff73e1a0c52dbc5e,
  0x3f32be41f8ba8b90, 0x72f3825c514d8bb8, 0xe4f28307980edcd7, 0x373036b9647f0a8b,
};
// clang-format on

INSTANTIATE_TEST_SUITE_P(ShiftTest, ShiftTest,
                         testing::Combine(testing::ValuesIn(kShiftVectors),
                                          testing::Range(0, 63)));

struct Bswap32Vector {
  uint32_t a, b;
  friend void operator<<(std::ostream &out, const Bswap32Vector &v) {
    out << "{" << v.a << ", " << v.b << "}";
  }
};

class Bswap32Test : public testing::TestWithParam<Bswap32Vector> {};

TEST_P(Bswap32Test, Bswap32) {
  EXPECT_EQ(_ot_builtin_bswap_i32(GetParam().a), GetParam().b);
}

// Simple python snippet for generating vectors:
//
// import random
// def bswap(x):
//   enc = x.to_bytes(4, byteorder='little')
//   return int.from_bytes(enc, byteorder='big', signed=False)
// for _ in range(0, 16):
//   line = []
//   for _ in range (0, 4):
//     a = random.getrandbits(32)
//     line.append(f"{{0x{a:08x}, 0x{bswap(a):08x}}},")
//   print(" ".join(line))
// clang-format off
constexpr Bswap32Vector kBswap32Vectors[] = {
  {0xf695f7f1, 0xf1f795f6}, {0x0c607801, 0x0178600c}, {0x59ffdc85, 0x85dcff59}, {0xaa655be2, 0xe25b65aa},
  {0xaa7330bd, 0xbd3073aa}, {0xb6d355da, 0xda55d3b6}, {0xdb93201c, 0x1c2093db}, {0x24f94ecc, 0xcc4ef924},
  {0x1ac3b892, 0x92b8c31a}, {0xb82dd98f, 0x8fd92db8}, {0x22d45753, 0x5357d422}, {0x87d7b7c0, 0xc0b7d787},
  {0x51c134c8, 0xc834c151}, {0xb75bd393, 0x93d35bb7}, {0x4d728dd5, 0xd58d724d}, {0x82219f2c, 0x2c9f2182},
  {0xe5d4039f, 0x9f03d4e5}, {0xdad1b011, 0x11b0d1da}, {0x842cda8a, 0x8ada2c84}, {0xd0c139a3, 0xa339c1d0},
  {0x9457a5bd, 0xbda55794}, {0x3c9c4149, 0x49419c3c}, {0xfc885a02, 0x025a88fc}, {0xfcd849e1, 0xe149d8fc},
  {0xe74ea521, 0x21a54ee7}, {0xbd5cb79e, 0x9eb75cbd}, {0x64b922b6, 0xb622b964}, {0x83e4a528, 0x28a5e483},
  {0x37e301e2, 0xe201e337}, {0x30af4e73, 0x734eaf30}, {0x57844ed8, 0xd84e8457}, {0xf69e4821, 0x21489ef6},
  {0xedc4c65a, 0x5ac6c4ed}, {0x64864ea9, 0xa94e8664}, {0x62dc20bc, 0xbc20dc62}, {0x00512130, 0x30215100},
  {0x0e18fd9e, 0x9efd180e}, {0x53f3a1cc, 0xcca1f353}, {0xc667d326, 0x26d367c6}, {0x5158bd5c, 0x5cbd5851},
  {0xddbe895f, 0x5f89bedd}, {0xa2ff822a, 0x2a82ffa2}, {0xefa5f882, 0x82f8a5ef}, {0x7bf9d61a, 0x1ad6f97b},
  {0xfe5a30cf, 0xcf305afe}, {0x197df65f, 0x5ff67d19}, {0xab90efaf, 0xafef90ab}, {0x92d0ba32, 0x32bad092},
  {0x7649bf9f, 0x9fbf4976}, {0x3e98ff6a, 0x6aff983e}, {0x1069fd34, 0x34fd6910}, {0x1e9ed02c, 0x2cd09e1e},
  {0x1d0a8e62, 0x628e0a1d}, {0xc3aa9fb8, 0xb89faac3}, {0xf249d680, 0x80d649f2}, {0x50d8dea1, 0xa1ded850},
  {0xa15582fb, 0xfb8255a1}, {0xc85ebafd, 0xfdba5ec8}, {0x59f83e7e, 0x7e3ef859}, {0x74642a4e, 0x4e2a6474},
  {0xfb5833aa, 0xaa3358fb}, {0x0d6051bc, 0xbc51600d}, {0x81ab8c1d, 0x1d8cab81}, {0x8aa14e9d, 0x9d4ea18a},
};
// clang-format on

INSTANTIATE_TEST_SUITE_P(Bswap32Test, Bswap32Test,
                         testing::ValuesIn(kBswap32Vectors));

struct CountBitsVector {
  uint32_t bits, count;
  friend void operator<<(std::ostream &out, const CountBitsVector &v) {
    out << "{0b" << std::bitset<32>(v.bits) << ", " << v.count << "}";
  }
};

class Popcount32Test : public testing::TestWithParam<CountBitsVector> {};

TEST_P(Popcount32Test, Popcount32) {
  EXPECT_EQ(_ot_builtin_popcount_i32(GetParam().bits), GetParam().count);
}

TEST_P(Popcount32Test, Parity32) {
  EXPECT_EQ(_ot_builtin_parity_i32(GetParam().bits), GetParam().count % 2);
}

// Simple python snippet for generating vectors:
//
// import random
// def popcnt(x):
//   return bin(x).count('1')
// for _ in range(0, 32):
//   line = []
//   for _ in range (0, 2):
//     a = random.getrandbits(32)
//     line.append(f"{{0b{a:032b}, {popcnt(a)}}},")
//   print(" ".join(line))
// clang-format off
constexpr CountBitsVector kPopcount32Vectors[] = {
  {0, 0}, {UINT32_MAX, 32},  // Trivial cases.

  {0b10010100010001010000001100111111, 14}, {0b11011001111100101001100001010100, 16},
  {0b00000000110111100011110111011111, 18}, {0b10101111100101100010000001111001, 16},
  {0b11000100010100101111011011100000, 15}, {0b11111101100111111010010000101000, 18},
  {0b00010001101111001100011000101011, 15}, {0b11101111000001000000110000101011, 14},
  {0b00110010011010111010011001111001, 17}, {0b10000110011010110000010000110000, 11},
  {0b00111111010000001010000010111101, 15}, {0b00100111011111011001111101101111, 22},
  {0b11100011100110000011111110000110, 17}, {0b00111100101000100010101000110011, 14},
  {0b01000011010000001010100111011101, 14}, {0b00010101000011001011100000011110, 13},
  {0b00011110111001101111000110000100, 16}, {0b10000110000010000100000100101001, 9},
  {0b00001111010011010100000000010101, 12}, {0b00110111100111000001011111111111, 21},
  {0b01001000000110100011011001010011, 13}, {0b11111100110111110110100111011010, 22},
  {0b00000001110111011000111100010101, 15}, {0b00010010101100101001100010001000, 11},
  {0b10110110000010001011111110010001, 16}, {0b11011101000111101010001011001001, 17},
  {0b00011000110101001100000001001100, 11}, {0b10101011011001011001100110011010, 17},
  {0b01001000000011111001111100001010, 14}, {0b00111110101010101011001010101011, 18},
  {0b10110000110100110111010110110011, 18}, {0b00110101110110101011101101010110, 19},
  {0b00000111110111010110010101000001, 15}, {0b10101000010001011010001010010111, 14},
  {0b00001100011011111010100010011001, 15}, {0b11000000100000010000000110100101, 9},
  {0b01101100010001100101110010011001, 15}, {0b01101011001111111010010111000110, 19},
  {0b00101101010100010010010110010100, 13}, {0b00100001100010001001100100100000, 9},
  {0b10101011100100110011011000000011, 15}, {0b10000000110010011111011010111011, 17},
  {0b01011100100101100000111001000011, 14}, {0b11101000110001110100101111101001, 18},
  {0b10010010010000001101001111110011, 15}, {0b10100100100011110100001010010110, 14},
  {0b11011000000001111100000010111001, 14}, {0b00010110000010010011000100101100, 11},
  {0b00010100110010010010001110110110, 14}, {0b01001001001100011010011010000000, 11},
  {0b10101010000001110110111111011101, 19}, {0b11011110100111001010010000000111, 16},
  {0b01000110001011110010111111100000, 16}, {0b01100111001111011001101100111010, 19},
  {0b00011000010001100100000001110110, 11}, {0b01100000100101001011000010010101, 12},
  {0b01000110110101110100101010111110, 18}, {0b11010110111010111001110100111110, 21},
  {0b01100001100011100011101000010110, 14}, {0b01001101010010001111100110101010, 16},
  {0b00110010100011111111101001111010, 19}, {0b11010010110101110010100100100101, 16},
  {0b00111100100001101010011101101100, 16}, {0b01010101111000010100001001011101, 15},
};
// clang-format on

INSTANTIATE_TEST_SUITE_P(Popcount32Test, Popcount32Test,
                         testing::ValuesIn(kPopcount32Vectors));

class Ctz32Test : public testing::TestWithParam<CountBitsVector> {};

TEST_P(Ctz32Test, Ctz32) {
  EXPECT_EQ(_ot_builtin_ctz_i32(GetParam().bits), GetParam().count);
}

TEST_P(Ctz32Test, Ffs32) {
  EXPECT_EQ(_ot_builtin_find_first_i32(GetParam().bits), GetParam().count + 1);
}

// Simple python snippet for generating vectors:
//
// import random
// for i in range(0, 32):
//   line = []
//   for _ in range (0, 2):
//     a = random.getrandbits(32)
//     a &= ((1 << (32 - i)) - 1) << i
//     a |= (1 << i)
//     line.append(f"{{0b{a:032b}, {i}}},")
//   print(" ".join(line))
// clang-format off
constexpr CountBitsVector kCtz32Vectors[] = {
  {0b00011101000010010001101001101011, 0},  {0b10101100110101111100001001101011, 0},
  {0b00101000001011000000001110010110, 1},  {0b10101111000001011001011010001110, 1},
  {0b00111111010100111101010010001100, 2},  {0b00010110010100010110110011000100, 2},
  {0b01000101111100100000101010101000, 3},  {0b10111010011000111010100100001000, 3},
  {0b00001011001110000100011110110000, 4},  {0b00010100010100110011011101010000, 4},
  {0b00001101110001110001011010100000, 5},  {0b00010100001011100110110101100000, 5},
  {0b11010011000011111101100101000000, 6},  {0b01100110101111101011101011000000, 6},
  {0b11001101011101101111011010000000, 7},  {0b00011010101100101000010110000000, 7},
  {0b10100001011010010100100100000000, 8},  {0b11001001011000010111100100000000, 8},
  {0b10010010001110001100101000000000, 9},  {0b10000111000010111110101000000000, 9},
  {0b10000111111100010001010000000000, 10}, {0b01111101101111001010110000000000, 10},
  {0b01010011000001101101100000000000, 11}, {0b00110110001011110011100000000000, 11},
  {0b00101001111000101101000000000000, 12}, {0b10110010010101010011000000000000, 12},
  {0b00010100011001001010000000000000, 13}, {0b00111110000111001010000000000000, 13},
  {0b11100100011011110100000000000000, 14}, {0b11101001111011010100000000000000, 14},
  {0b00100111001001011000000000000000, 15}, {0b11011010111000111000000000000000, 15},
  {0b00100001111111110000000000000000, 16}, {0b10100100100010110000000000000000, 16},
  {0b10100100011110100000000000000000, 17}, {0b11111001100101100000000000000000, 17},
  {0b01111001101011000000000000000000, 18}, {0b10110010001001000000000000000000, 18},
  {0b00110100110110000000000000000000, 19}, {0b00001010100110000000000000000000, 19},
  {0b00011010100100000000000000000000, 20}, {0b00110101110100000000000000000000, 20},
  {0b11100100111000000000000000000000, 21}, {0b01100010101000000000000000000000, 21},
  {0b10100001110000000000000000000000, 22}, {0b11000110010000000000000000000000, 22},
  {0b10101011100000000000000000000000, 23}, {0b01000001100000000000000000000000, 23},
  {0b00110001000000000000000000000000, 24}, {0b01000011000000000000000000000000, 24},
  {0b11001110000000000000000000000000, 25}, {0b11011110000000000000000000000000, 25},
  {0b01010100000000000000000000000000, 26}, {0b10010100000000000000000000000000, 26},
  {0b00011000000000000000000000000000, 27}, {0b01101000000000000000000000000000, 27},
  {0b11010000000000000000000000000000, 28}, {0b11010000000000000000000000000000, 28},
  {0b10100000000000000000000000000000, 29}, {0b10100000000000000000000000000000, 29},
  {0b01000000000000000000000000000000, 30}, {0b01000000000000000000000000000000, 30},
  {0b10000000000000000000000000000000, 31}, {0b10000000000000000000000000000000, 31},
};
// clang-format on

INSTANTIATE_TEST_SUITE_P(Ctz32Test, Ctz32Test,
                         testing::ValuesIn(kCtz32Vectors));

class Clz32Test : public testing::TestWithParam<CountBitsVector> {};

TEST_P(Clz32Test, Clz32) {
  EXPECT_EQ(_ot_builtin_clz_i32(GetParam().bits), GetParam().count);
}

// Simple python snippet for generating vectors:
//
// import random
// for i in range(0, 32):
//   line = []
//   for _ in range (0, 2):
//     a = random.getrandbits(32)
//     a &= ((1 << (32 - i)) - 1);
//     a |= (1 << (32 - i - 1))
//     line.append(f"{{0b{a:032b}, {i}}},")
//   print(" ".join(line))
// clang-format off
constexpr CountBitsVector kClz32Vectors[] = {
  {0b10000011010010000110000111110001, 0},  {0b11110101110110101110000011011000, 0},
  {0b01000001101011100111011110110011, 1},  {0b01001010110100011100010100011110, 1},
  {0b00100010111110110100101110101100, 2},  {0b00111010100110000100101010001100, 2},
  {0b00010001101100110110001100011101, 3},  {0b00010010011100011011001101100101, 3},
  {0b00001001001001110110000001110110, 4},  {0b00001010001110000111111000010010, 4},
  {0b00000111000101010000110010111111, 5},  {0b00000100101001000010000000000011, 5},
  {0b00000010010110111110111000001101, 6},  {0b00000011101110000111010000011110, 6},
  {0b00000001011100000010000000101110, 7},  {0b00000001111100100011100101110111, 7},
  {0b00000000110011110101101011110000, 8},  {0b00000000111010001111010000111010, 8},
  {0b00000000010100001111111010100011, 9},  {0b00000000010101011000101101010100, 9},
  {0b00000000001110010111001101001111, 10}, {0b00000000001111011011001101000000, 10},
  {0b00000000000111001110101010111111, 11}, {0b00000000000111101011000001011011, 11},
  {0b00000000000011001101111110000011, 12}, {0b00000000000010010000010101101011, 12},
  {0b00000000000001110111101010111001, 13}, {0b00000000000001011100010000110110, 13},
  {0b00000000000000101111011011011000, 14}, {0b00000000000000111101110111000001, 14},
  {0b00000000000000011100111010000011, 15}, {0b00000000000000010001100111101010, 15},
  {0b00000000000000001000001010011110, 16}, {0b00000000000000001001100110101111, 16},
  {0b00000000000000000110100100101111, 17}, {0b00000000000000000111101111000110, 17},
  {0b00000000000000000010001101101010, 18}, {0b00000000000000000010000101011100, 18},
  {0b00000000000000000001100110011000, 19}, {0b00000000000000000001000000001010, 19},
  {0b00000000000000000000100111011101, 20}, {0b00000000000000000000110100101001, 20},
  {0b00000000000000000000011000100100, 21}, {0b00000000000000000000011001001000, 21},
  {0b00000000000000000000001000010111, 22}, {0b00000000000000000000001010010010, 22},
  {0b00000000000000000000000101110010, 23}, {0b00000000000000000000000101111010, 23},
  {0b00000000000000000000000010100100, 24}, {0b00000000000000000000000011100110, 24},
  {0b00000000000000000000000001011100, 25}, {0b00000000000000000000000001111100, 25},
  {0b00000000000000000000000000111000, 26}, {0b00000000000000000000000000111000, 26},
  {0b00000000000000000000000000010100, 27}, {0b00000000000000000000000000010100, 27},
  {0b00000000000000000000000000001000, 28}, {0b00000000000000000000000000001101, 28},
  {0b00000000000000000000000000000100, 29}, {0b00000000000000000000000000000111, 29},
  {0b00000000000000000000000000000011, 30}, {0b00000000000000000000000000000010, 30},
  {0b00000000000000000000000000000001, 31}, {0b00000000000000000000000000000001, 31},
};
// clang-format on

INSTANTIATE_TEST_SUITE_P(Clz32Test, Clz32Test,
                         testing::ValuesIn(kClz32Vectors));

}  // namespace
}  // namespace math_unittest